//
//  ContentView.swift
//  FuncTest
//
//  Created by li on 2025-04-03.
//

import SwiftUI

public struct MGWallet {
	
	public enum MoveOutDirection {
		case left, right, top, bottom
		
		public var isHorizontal: Bool {
			self == .left || self == .right
		}
		
	}
	/// 显示的除了中间的额外个数
	public var cacheCount: Int = 3
	/// 堆叠的间隙
	public var spacing: CGFloat = 10
	/// 缩放的最小比例
	public var minScale: CGFloat = 0.8
	/// 移除的方向
	public var moveOutDirection: MGWallet.MoveOutDirection = .right
	/// 卡片移除，或移入距离现有折叠卡片的间隙
	public var moveOutSpacing: CGFloat = 20
}

public struct MGWalletView<D, V>: View where D: Identifiable, V: View {
	
	public init(currentIndex: Binding<Int>, config: MGWallet, data: [D], @ViewBuilder content: @escaping (D) -> V) {
		
		self._currentIndex = currentIndex
		self.config = config
		self.data = data
		self.content = content
	}
	
	@Binding var currentIndex: Int
	var config: MGWallet = .init()
	var data: [D]
	 
	@ViewBuilder var content: (D) -> V
	
	enum Direction {
		case next, last, none
	}
	
	@State private var dragSize: CGSize = .zero
	@State private var currentSize: CGSize = .zero

	@State var direction: Direction = .none
	/// 正在减速动画
	@State private var isSlowingDownAnimation: Bool = false
	@State private var proxy: GeometryProxy?
	@State private var isDraging: Bool = false
	
	public var body: some View {
		
		GeometryReader { proxy in
			
			let gesture = DragGesture()
				.onChanged { value in
					isDraging =  true
					dragSize = transformDragSize(value.translation, page: proxy.size)

				}
				.onEnded { value in
					
					endDrag(value, proxy: proxy)
				}
			
			ZStack {
				
				ForEach( -config.cacheCount - 1..<(config.cacheCount + 2), id: \.self) { index in
					
					if let model = data(index) {

						content(model)
							.zIndex(zIndex(index))
							.frame(maxWidth: .infinity, maxHeight: .infinity)
							.scaleEffect(transScaleValue(index, proxy: proxy))
							.offset(transOffsetValue(index, proxy: proxy))
							.opacity(walletOpacity(index: index, proxy: proxy))
					}
				}
			}
			.gesture(gesture)
			.onAppear {
				self.proxy = proxy
			}
		}
	}
	
	func zIndex(_ index: Int) -> Double {
		let reverse: Double = config.moveOutDirection
			== .right || config.moveOutDirection == .bottom ? -1 : 1
		return -Double(index) * reverse
	}
	
	func walletOpacity(index: Int, proxy: GeometryProxy) -> CGFloat {
		
		let reverse: Bool =  config.moveOutDirection == .right  || config.moveOutDirection == .bottom ? true : false
		
		let isHorizontal = config.moveOutDirection.isHorizontal
		let dragLength = isHorizontal ? dragSize.width : dragSize.height
	
		if reverse {

			if index <= 0 {
			
				return 1
			}else {
				return isDraging && dragLength < 0 ? 1 : 0
			}
		}else {
			if index >= 0 {
			
				return 1
			}else {
				return isDraging && dragLength > 0 ? 1 : 0
			}
		}
	}
	
	
	
	func endDrag(_ value: _ChangedGesture<DragGesture>.Value, proxy: GeometryProxy) {
		
		var endSize: CGSize = .zero
		var duration: TimeInterval = 0.25
		
		let page = config.moveOutDirection.isHorizontal ? proxy.size.width : proxy.size.height
		let isHorizontal = config.moveOutDirection.isHorizontal
		let dragLength = isHorizontal ? dragSize.width : dragSize.height
		
		let progress = min(1, max(0, 1 - abs(dragLength) / page))
		duration = duration * progress
		
		
		var direction: Direction = .none
		
		if dragLength > 0 {
			if dragLength > 30 || dragLength > page / 4.0 {

				direction = .last
				if isHorizontal {
					endSize = .init(width: page, height: 0)
				}else {
					endSize = .init(width: 0, height: page)
				}
			}
		}else if dragLength < 0 {
			if dragLength < -30 || dragLength < -page / 4.0  {

				direction = .next
				if isHorizontal {
					endSize = .init(width: -page, height: 0)
				}else {
					endSize = .init(width: 0, height: -page)
				}
			}
		}
		
		endSize = .init(width: currentSize.width  + endSize.width, height: currentSize.height + endSize.height)

		withAnimation(.linear(duration: duration)) {

			dragSize = endSize
		}
		
		DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
			
			isDraging = false
			var index = currentIndex
			if direction == .next {

				index += 1
			}else if direction == .last {

				index -= 1
			}
			
			if index >= 0 {
				index = index % data.count
			}else {
				index = index % data.count + data.count
				
 			}
			currentIndex = index
			currentSize = .zero
			dragSize = .zero
		}
	}
	
	func transformDragSize(_ move: CGSize, page: CGSize) -> CGSize {
		
		var width = move.width
		var height = move.height
		
		if width > 0 {
			
			width = min(width, page.width)
		}else {
			width = max(width, -page.width)
		}
		
		if height > 0 {
			
			height = min(height, page.height)
		}else {
			height = max(height, -page.height)
		}
		
		return CGSize(width: width, height: height)
	}

	
	func data(_ index: Int) -> D? {
		
		let count = data.count
		guard count > 0 else {
			return nil
		}
		
		var index = currentIndex + index
		
		if index < 0 {
			index = count + index % count
		}
		
		if index >= count {
			index = index % count
		}
		
		let result = data[index]
		return result
	}
	
	func transDragValue(_ index: Int, proxy: GeometryProxy) -> CGFloat  {
		
		let page = config.moveOutDirection.isHorizontal ? proxy.size.width : proxy.size.height
		
		
		let currentLength = config.moveOutDirection.isHorizontal ? currentSize.width : currentSize.height
		let dragLength = config.moveOutDirection.isHorizontal ? dragSize.width : dragSize.height
		let offsetLength = CGFloat(index) * page
		
		let move = currentLength + dragLength + offsetLength
		
		let reverse: CGFloat = config.moveOutDirection == .right || config.moveOutDirection == .bottom ? -1 : 1
		return move * reverse
	}
	
	func transScaleValue(_ index: Int, proxy: GeometryProxy) -> CGFloat {
		
		let page =  config.moveOutDirection.isHorizontal ? proxy.size.width :  proxy.size.height
		
		let move = transDragValue(index, proxy: proxy)
		
		var scale: CGFloat = 1.0
		if move > 0 {
			
			let cachePageLength: CGFloat = CGFloat(config.cacheCount) * page
			if move <= cachePageLength {
				scale = 1 - min(1, move / cachePageLength) * (1 - config.minScale)
			}else {
				scale = config.minScale
			}
		}

		return scale
	}
	

	func transOffsetValue(_  index: Int, proxy: GeometryProxy) -> CGSize {
		
		let reverse: CGFloat = config.moveOutDirection == .right || config.moveOutDirection == .bottom ? -1 : 1
		let move = transDragValue(index, proxy: proxy)
		let scale = transScaleValue(index, proxy: proxy)
		let page = config.moveOutDirection.isHorizontal ? proxy.size.width : proxy.size.height
		
		let scaleLength = page * (1 - scale) / 2.0 * reverse
		
		var result = move
		
		if move >= 0 {
			
			/// 正在滑动的最大偏移量
			let maxLength: CGFloat = CGFloat(config.cacheCount) * config.spacing * reverse
			
			/// 最大循环的拖拽距离
			let cycleLength: CGFloat = CGFloat(config.cacheCount) * page
			
			if move <= cycleLength {
				let scale = move / cycleLength
				result = maxLength * min(1, scale)
			}else {
				result = maxLength
			}
			
		}else {
			
			result = move * (page + config.moveOutSpacing) / page * reverse
		}
		
		result += scaleLength
		
		if config.moveOutDirection.isHorizontal {
			return CGSize(width: result, height: 0)
		}else {
			return CGSize(width: 0, height: result)
		}
	}
	
}

struct MGWalletViewTest: View {
	@State var currentIndex: Int = 4
	@State var config = MGWallet(moveOutDirection: .left)
	var datas: [WalletData] = [
		.init(color: .blue, text: "中信1"),
		.init(color: .red, text: "建行2"),
		.init(color: .green, text: "工行3"),
		.init(color: .orange, text: "农行4"),
		.init(color: .purple, text: "平安5"),
		.init(color: .gray, text: "平安6"),
		.init(color: .black, text: "邮政7"),
		.init(color: .pink, text: "光大8"),
		.init(color: .yellow, text: "普惠9"),
	]

	var body: some View {

		MGWalletView(currentIndex: $currentIndex, config: config, data: datas) { model in
			WalletCardView(data: model)
		}
		.padding(.horizontal,  40)
	
	}
	
	struct WalletData: Hashable, Identifiable {
		var id: UUID = .init()
		
		var color: Color
		var text: String
	}

	struct WalletCardView: View {
		let data: WalletData
		var body: some View {
			ZStack {
				Text(data.text)
					.foregroundColor(.white)
					.font(.system(size: 22, weight: .bold))
					.padding(.top, 20)
			}
			.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
			.background(
				data.color
					.cornerRadius(8)
			)
		}
		
	}
}

#Preview {
	
	MGWalletViewTest()
	
}

 
